package client;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import utils.MethodSignatureParser;
import utils.ObjectParser;
import org.apache.xmlrpc.XmlRpcException;
import org.w3c.dom.NodeList;
import utils.Logging;

/**
 * RPC Client loads all method signatures of an XML RPC service from an XML configuration file, and provides ways to
 * retrieve any results after executing each method on that service.
 * @date Mar 16, 2011
 * @author Eric Kisner
 */
public class RPCClient {

    private static final String PROTOCOL = "http://";
    private static final String DEFAULT_URL = "localhost";
    /**
     * Method names of any methods we are calling on the server.
     */
    private static final List<String> methodNames = new ArrayList<String>();
    /**
     * String representations of the return types for each method we are calling.
     */
    private static final List<String> returnTypes = new ArrayList<String>();
    /**
     * Vector representation of any parameters we need to pass into any methods.
     */
    private static final List<Vector> params = new ArrayList<Vector>();
    private RPCConnection conn;
    private MethodSignatureParser signatureParser;

    /**
     * Default constructor.
     * @param port The port that the XML RPC service is on.
     * @param URL The URL to the XML RPC service.
     * @param handler The handler that the XML RPC service uses.
     * @param configFile The XML configuration file path that has method signatures for all methods on the service.
     */
    public RPCClient( int port, String URL, String handler, String configFile ) {
        String initStr = PROTOCOL + ((URL == null) ? DEFAULT_URL : URL) + ":" + port + "/";
        conn = new RPCConnection( initStr, handler );
        signatureParser = new MethodSignatureParser( configFile );
        if( conn == null ) {
            throw new RuntimeException( "There was an error instantiating the connection to the XML RPC service." );
        } else if( signatureParser == null ) {
            throw new RuntimeException( "There was an error loading the XML configuration file '" + configFile + "'." );
        }
        loadSignatures();
    }

    /**
     * Load all method signatures that exist in the XML config file for the specified XML RPC service.
     */
    private void loadSignatures() {
        loadMethods();
        loadParameters();
        loadReturnTypes();
    }

    /**
     * Get all method names we are going to execute from the service.
     */
    private void loadMethods() {
        methodNames.addAll( signatureParser.getAllMethodNames() );
    }

    /**
     * Load all parameters for every method on the service.
     */
    private void loadParameters() {
        for( String s : methodNames ) {
            //For each Method, get the parameters that the method requires.
            NodeList paramNodeList = signatureParser.getAllParams( signatureParser.getMethodNode( s ) );
            if( paramNodeList.getLength() > 0 ) {
                //For each parameter that the method requires, get the user-specified value that the
                //parameter has.
                Vector newParam = new Vector();
                for( int i = 0; i < paramNodeList.getLength(); i++ ) {
                    String paramValue = signatureParser.getParamValue( paramNodeList.item( i ) );
                    if( paramValue.equals( "" ) ) {
                        Logging.log( "The parameter values for method '" + s + "' is incomplete. "
                                     + "Please input a value and re-run this program.", this.getClass() );
                        throw new RuntimeException( "The parameter values for method '" + s + "' is incomplete. "
                                                    + "Please input a value and re-run this program." );
                    }
                    newParam.addElement( Double.parseDouble( paramValue ) );
                }
                params.add( newParam );
            } else {
                //No parameters exist for the current method.
                params.add( new Vector() );
            }
        }
    }

    /**
     * Load all return types for each method.
     */
    private void loadReturnTypes() {
        returnTypes.addAll( signatureParser.getAllReturnTypes() );
    }

    /**
     * Get the list of values that are returned after executing each method and return a mapping of method names to
     * values.
     * @return Map of method names to their results after executing them.
     */
    public HashMap<String, String> getResults() throws XmlRpcException, IOException, RuntimeException {
        //Execute all methods with their respective parameters.
        List<Object> objValues = conn.getResults( methodNames, params );
        if( objValues.isEmpty() ) {
            throw new RuntimeException(
                    "There was an error invoking methods. Please check the log file for more details." );
        }
        //Get the string values of all returned objects.
        List<String> strValues = ObjectParser.runParseObjectMethods( returnTypes, objValues );
        if( strValues.isEmpty() ) {
            throw new RuntimeException(
                    "There was an error parsing objects. Please check the log file for more details." );
        }
        HashMap<String, String> mapping = new HashMap<String, String>();
        //The list of methods should be the same size and in the same order as the returned values.
        for( int i = 0; i < methodNames.size(); i++ ) {
            mapping.put( methodNames.get( i ), strValues.get( i ) );
        }
        return mapping;
    }
}
